﻿// HSS.Data.DataAccessContext.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       DataAccessContext.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Data
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	#endregion

	#region ResolveContextConnectionEventArgs
	/// <summary>
	/// Event argument for the DataContext ResolveConnection event.
	/// </summary>
	public sealed class ResolveContextConnectionEventArgs : EventArgs
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		public ResolveContextConnectionEventArgs()
		{

		}

		/// <summary>
		/// The Connnection Key.
		/// </summary>
		public Type ConnectionKey { get; set; }
		/// <summary>
		/// The resolved connection.
		/// </summary>
		public DataAccessConnection Connection { get; set; }
	}
	#endregion

	#region DataAccessContext
	/// <summary>
	/// DataAccessContext exposes the current globally available Default <see cref="DataAccessConnection"/> which
	/// is consumed by the <see cref="DataAccessService{T}"/>.
	/// </summary>
	public static class DataAccessContext
	{
		#region Events
		/// <summary>
		/// An event fired when getting a registered connection that is not found,
		/// allowing a subscriber to provide a connection to be registered for the
		/// connection key.
		/// </summary>
		public static event EventHandler<ResolveContextConnectionEventArgs> ResolveConnection;
		private static bool OnResolveConnection(ResolveContextConnectionEventArgs args)
		{
			var handler = ResolveConnection;
			if (null != handler)
			{
				try
				{
					handler(null, args);
					if (null != args.Connection)
					{
						connections.Add(args.ConnectionKey, args.Connection);
						return true;
					}
				}
				catch
				{
					args.Connection = null;
				}
			}
			return false;
		}
		#endregion

		#region Fields
		static readonly object monitor = new object();
		static Dictionary<Type, DataAccessConnection> connections = new Dictionary<Type, DataAccessConnection>();
		#endregion

		#region Properties
		/// <summary>
		/// Gets or sets the default <see cref="DataAccessConnection"/> instance.
		/// </summary>
		public static DataAccessConnection DefaultConnection
		{
			get;
			set;
		}
		#endregion

		#region Methods
		/// <summary>
		/// Attempts to get the default connection string based on the current Connection (<see cref="DataAccessConnection"/>) settings.
		/// </summary>
		/// <returns>A connection string based on the current Connection (<see cref="DataAccessConnection"/>) settings.</returns>
		/// <exception cref="DataAccessConnectionNullException">DefaultConnection is null.</exception>
		public static string GetDefaultConnectionString()
		{
			if (null == DefaultConnection)
				throw new DataAccessConnectionNullException("DefaultConnection is null.");
			return DefaultConnection.ConnectionString;
		}
		/// <summary>
		/// Returns if a <see cref="DataAccessConnection"/> has been registered for the given TKey.
		/// </summary>
		/// <typeparam name="TKey">The Type to evaluate.</typeparam>
		/// <returns>true if a <see cref="DataAccessConnection"/> has been registered for the given TKey; otherwise false.</returns>
		public static bool IsConnectionRegistered<TKey>()
		{
			lock (monitor)
				return connections.ContainsKey(typeof(TKey));
		}
		/// <summary>
		/// Registers the provided <see cref="DataAccessConnection"/> for a specific TKey.
		/// </summary>
		/// <typeparam name="TKey">The Type to register the provided <paramref name="connection"/> to.</typeparam>
		/// <param name="connection">The connection to register.</param>
		/// <exception cref="DuplicateConnectionException">Duplicate Connection.</exception>
		public static void RegisterConnection<TKey>(DataAccessConnection connection)
		{
			lock (monitor)
			{
				Guard.ArgumentNotNull(connection, "connection");
				var t = typeof(TKey);
				if (connections.ContainsKey(t))
					throw new DuplicateConnectionException();
				connections.Add(t, connection);
			}
		}
		/// <summary>
		/// Unregisters a previously registered <see cref="DataAccessConnection"/> for a specific TKey.
		/// </summary>
		/// <typeparam name="TKey">The Type to unregister.</typeparam>
		/// <returns>true if a connection for the specified type existed and removed; otherwise false.</returns>
		public static bool UnregisterConnection<TKey>()
		{
			lock (monitor)
				return connections.Remove(typeof(TKey));
		}
		/// <summary>
		/// Attempts to retrieve a specific <see cref="DataAccessConnection"/>.
		/// </summary>
		/// <typeparam name="TKey">The Type to retrieve.</typeparam>
		/// <returns>The registered <see cref="DataAccessConnection"/> if found.</returns>
		/// <exception cref="ConnectionNotFoundException">No connection found for the specified Type.</exception>
		public static DataAccessConnection GetConnection<TKey>()
		{
			lock (monitor)
			{
				DataAccessConnection connection = null;
				if (!connections.TryGetValue(typeof(TKey), out connection))
				{
					var args = new ResolveContextConnectionEventArgs { ConnectionKey = typeof(TKey) };
					if (OnResolveConnection(args))
						return args.Connection;
					throw new ConnectionNotFoundException();
				}
				return connection;
			}
		}
		/// <summary>
		/// Attempts to retrieve a specific <see cref="DataAccessConnection"/>.
		/// </summary>
		/// <typeparam name="TKey">The Type to retrieve.</typeparam>
		/// <param name="connection">When this method returns, contains the <see cref="DataAccessConnection"/> associated with the specified TKey, if the TKey was found; otherwise null.</param>
		/// <returns>true if a registered <see cref="DataAccessConnection"/> was found for the specified TKey; otherwise, false.</returns>
		/// <exception cref="System.ArgumentNullException">key is null.</exception>
		public static bool TryGetConnection<TKey>(out DataAccessConnection connection)
		{
			lock (monitor)
			{
				if (!connections.TryGetValue(typeof(TKey), out connection))
				{
					var args = new ResolveContextConnectionEventArgs { ConnectionKey = typeof(TKey) };
					if (OnResolveConnection(args))
					{
						connection = args.Connection;
						return true;
					}
				}
				return false;
			}
		}
		/// <summary>
		/// Attempts to retrieve a specific <see cref="DataAccessConnection"/> connection string.
		/// </summary>
		/// <typeparam name="TKey">The Type to retrieve.</typeparam>
		/// <returns>The connection string if a registered <see cref="DataAccessConnection"/> was found.</returns>
		/// <exception cref="ConnectionNotFoundException">No connection found for the specified Type.</exception>
		public static string GetConnectionString<TKey>()
		{
			lock (monitor)
			{
				DataAccessConnection connection = null;
				if (!connections.TryGetValue(typeof(TKey), out connection))
				{
					var args = new ResolveContextConnectionEventArgs { ConnectionKey = typeof(TKey) };
					if (OnResolveConnection(args))
						return args.Connection.ConnectionString;
					throw new ConnectionNotFoundException();
				}
				return connection.ConnectionString;
			}
		}
		/// <summary>
		/// Attempts to retrieve a specific <see cref="DataAccessConnection"/> connnection string.
		/// </summary>
		/// <typeparam name="TKey">The Type to retrieve.</typeparam>
		/// <param name="connectionString">When this method returns, contains the connectionString associated with the specified TKey, if the TKey was found; otherwise null.</param>
		/// <returns>true if a registered <see cref="DataAccessConnection"/> was found for the specified TKey; otherwise, false.</returns>
		/// <exception cref="System.ArgumentNullException">key is null.</exception>
		public static bool TryGetConnectionString<TKey>(out string connectionString)
		{
			lock (monitor)
			{
				connectionString = string.Empty;
				DataAccessConnection connection;
				if (connections.TryGetValue(typeof(TKey), out connection))
				{
					connectionString = connection.ConnectionString;
					return true;
				}
				else
				{
					var args = new ResolveContextConnectionEventArgs { ConnectionKey = typeof(TKey) };
					if (OnResolveConnection(args))
					{
						connectionString = args.Connection.ConnectionString;
						return true;
					}
				}
				return false;
			}
		}
		/// <summary>
		/// Gets the registered <see cref="DataAccessConnection"/> if exists; otherwise the <see cref="DefaultConnection"/>.
		/// </summary>
		/// <typeparam name="TKey">The Type to retrieve.</typeparam>
		/// <returns>Either the registered <see cref="DataAccessConnection"/> or the <see cref="DefaultConnection"/>.</returns>
		public static DataAccessConnection GetRegisteredOrDefault<TKey>()
		{
			lock (monitor)
			{
				DataAccessConnection connection = null;
				if (connections.TryGetValue(typeof(TKey), out connection))
					return connection;
				var args = new ResolveContextConnectionEventArgs { ConnectionKey = typeof(TKey) };
				if (OnResolveConnection(args))
					return args.Connection;
				return DefaultConnection;
			}
		}
		#endregion
	}
	#endregion
}